/**
 * Copyright (C) 2008 GeoScheduler Team, as stated on <http://www.geoscheduler.org/authors>.
 * 
 * This file is part of GeoScheduler.
 * 
 * GeoScheduler is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * GeoScheduler is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with GeoScheduler. If not, see <http://www.gnu.org/licenses/>.
 */

package org.geoscheduler.commons.msgs;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;

import org.geoscheduler.commons.i18n.LocalizationSource;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

/**
 * 
 * <h2>JSONMessageMarshaller</h2>
 * <p>
 * JSONMarshaller for {@link ParametrizedMessage}
 * </p>
 * 
 * @author lko
 */
public class JSONMessageMarshaller {

	public static final String KEY_MESSAGE = "message";
	public static final String KEY_SEVERITY_ORDINAL = "sevordinal";
	public static final String KEY_SEVERITY_NAME = "sevname";
	public static final String KEY_PARAMS = "params";
	public static final String KEY_TYPE_CLASS = "typeClass";
	public static final String KEY_TYPE = "type";

	protected static final List<String> REQUIRED_KEYS = Arrays.asList(KEY_MESSAGE, KEY_PARAMS, KEY_TYPE_CLASS,
			KEY_TYPE, KEY_SEVERITY_NAME, KEY_SEVERITY_ORDINAL);

	static class MessageStub extends ParametrizedMessage {

		private final String strMessage;

		public MessageStub(Enum<?> message, String strMessage, MessageSeverity severity, String... parameters) {
			super(message, LocalizationSource.VOID_LOC_SOURCE, severity, parameters);
			this.strMessage = strMessage;
		}

		@Override
		public String getMessage(Locale locale) {
			return strMessage;
		}
	}

	public JSONObject marshall(Message message, Locale locale) throws JSONException {
		JSONObject obj = new JSONObject();
		obj.put(KEY_TYPE, message.getType());
		obj.put(KEY_TYPE_CLASS, message.getType().getClass());
		obj.put(KEY_MESSAGE, message.getMessage(locale));
		obj.put(KEY_PARAMS, new JSONArray(message.getParameters()));
		obj.put(KEY_SEVERITY_ORDINAL, message.getSeverity().getOrdinal());
		obj.put(KEY_SEVERITY_NAME, message.getSeverity().getName());
		return obj;
	}

	public Message unmarshall(JSONObject jObj) throws JSONException, IllegalArgumentException, IllegalStateException {
		assertIsMessage(jObj);
		return new MessageStub(parseType(jObj), null, parseSeverity(jObj), parseParams(jObj));
	}

	protected String parseMessage(JSONObject jObj) throws JSONException {
		return jObj.getString(KEY_MESSAGE);
	}

	protected Enum<?> parseType(JSONObject jObj) throws JSONException, IllegalStateException {
		String strTypeCls = jObj.getString(KEY_TYPE_CLASS);
		Class<? extends Enum> enumCls;
		try {
			enumCls = (Class<? extends Enum>) Class.forName(strTypeCls);
		} catch (ClassNotFoundException ex) {
			throw new IllegalStateException("Class for message type not found", ex);
		}
		return Enum.valueOf(enumCls, jObj.getString(KEY_TYPE));
	}

	protected void assertIsMessage(JSONObject jObj) {
		if (!isMessage(jObj)) {
			throw new IllegalArgumentException("Not a message object " + jObj);
		}
	}

	protected String[] parseParams(JSONObject jObj) throws JSONException {
		JSONArray jArray = jObj.getJSONArray(KEY_PARAMS);
		String[] ret = null;
		if (jArray != null) {
			int size = jArray.length();
			ret = new String[size];
			for (int i = 0; i < size; i++) {
				ret[i] = jArray.getString(i);
			}
		}
		return ret;
	}

	protected MessageSeverity parseSeverity(JSONObject jObj) throws JSONException {
		int ordinal = jObj.getInt(KEY_SEVERITY_ORDINAL);
		String name = jObj.getString(KEY_SEVERITY_NAME);
		return new MessageSeverity(name, ordinal);
	}

	public boolean isMessage(JSONObject jObj) {
		Set<String> reqKeys = new HashSet<String>(REQUIRED_KEYS);
		for (Iterator<String> kIt = jObj.keys(); kIt.hasNext();) {
			reqKeys.remove(kIt.next());
		}
		return reqKeys.isEmpty();
	}
}
